<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"> <meta name="viewport" content="width=device-width">
    <title>浅谈pytorch加载图片作为训练数据的一种姿势</title>
    <meta name="viewport" content="width=device-width,initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/css/bootstrap.min.css"
          integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
          crossorigin="anonymous">
    <link rel="stylesheet"
          href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/styles/androidstudio.min.css">
<!--    <link rel="stylesheet" href="/web_template_css.css">-->
    <!-- this is highlight.js -->
    <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
    <script id="MathJax-script" async
            src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">
    </script>
    <script  src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
    integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
    integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/js/bootstrap.min.js"
    integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/highlight.min.js"></script>
    <style>
      img{
        display: block;
        height: auto;
        max-width: 100%;
      }
    </style>
  </head>
  <body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="#">kvrmnks</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
          <ul class="navbar-nav">
            <li class="nav-item active">
              <a class="nav-link" href="./../../../../../index.html" target="_self">Home <span class="sr-only">(current)</span></a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="./../../../../../blog.html">Blog</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="./../../../../../about.html">About</a>
            </li>
          </ul>
        </div>
      </nav>



    <div class='container'>
      <p>本文以HWDB1.1 数据的处理作为例子，简单介绍如何简单地将图片作为训练/测试数据</p>

<hr>

<p>一般在深度学习中，训练数据集都十分庞大，无法一次加载到内存中，只能分批次加载到内存中去。</p>

<p>幸运地是 torchvision 中自带了一种可以分批次加载数据的方式。</p>

<h2>在硬盘上建立正确的文件结构</h2>

<p>对于HWDB1.1 这个数据集来说，一个文件中包含了许多的图片， 我们首先需要获取到这些图片和图片对应的 label，这一部分可以在 <a href="../../blog/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84%E8%A7%A3%E6%9E%90HWDB1.1%E6%95%B0%E6%8D%AE%E4%B9%8B%E8%B7%AF/index.md">从零开始的解析HWDB1.1数据之路</a>找到。</p>

<p>这个方法会返回features，labels数组。</p>

<p>假设我们要把所有的训练数据放在train_data文件夹下，我们需要建立这样一种结构</p>

<ol>
<li><p>每个图片存在于其label对应的文件夹内</p></li>
<li><p>同一个label的文件夹内图片名不能相同</p></li>
<li><p>各个label文件夹都在train_data下</p></li>
</ol>

<p>打个比方来说，有一张图片对应的label是1，那么它的路径就是/train_data/1/xxx</p>

<p>之后我们就可以用torchvision.datasets.ImageFolder 对这个结构进行解析，具体步骤见下文。</p>

<p><strong>注意这里有一个很严重的坑</strong></p>

<p>ImageFolder  会对每个label进行重新赋值为一个数字，具体来说就是，按照label名的字典序依次转化为0, 1, ...</p>

<p>所以如果train_data文件夹下有1, 2, 10这3个文件夹，ImageFolder转化10的label会被标成1而不是10，因为10作为一个字符串，排在字典序的第2位。</p>

<p>这里由于不是没有涉及太多科技，代码不再给出。</p>

<h2>如何解析这个文件结构 并且对读入的图片做预处理</h2>

<p>这里介绍的ImageFolder 与下一节的DataLoader是配套的，所以如果在这一节中没看懂“为什么这么做”，可以看一下下一节一起理解。</p>

<p>正如上一句所说，我们使用ImageFolder自动解析<q>数据结构</q>(并非那个数据结构)。</p>

<p>用法如下</p>

<pre><code class="language-python">from torchvision import datasets
from torchvision import transforms,utils
train_data = datasets.ImageFolder(&#39;train_data&#39;, transform=transforms.Compose([
    MyResize(64,64),
    transforms.ToTensor()
]))
</code></pre>

<p>第一个参数便是train_data这个文件夹的路径，可以看到第二个是个看不懂的参数（，这个参数有关加载之后图像的初始化。</p>

<p>现在就讲，不会咕不会咕。</p>

<p>首先trainsforms.Compose([...])，是将参数tuple里的参数依次执行。</p>

<p>先忽略原文中的那个MyResize(64,64)</p>

<p>先来看看那个transforms.ToTensor()</p>

<p>这是tranforms 自带的一个transform类，可以将图片转化为tensor数组，并且把图片的值域压缩到\([0,1]\)上，方便训练。</p>

<p>再来细说一下那个MyResize(64, 64), 这是一个自定义transform类，要自定义这样一个类需要定义\(\_\_call\_\_\)方法，即如下形式</p>

<pre><code class="language-python">class MyTransform():
    def __init__(self, ):
        # 初始化
        pass
    def __call__(self, img):
        # 对图像做一些操作
        return img
</code></pre>

<p>由于tranforms 自带的resize太拉胯了，我就自己写了resize，具体方法见 <a href="../..//blog/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84%E8%A7%A3%E6%9E%90HWDB1.1%E6%95%B0%E6%8D%AE%E4%B9%8B%E8%B7%AF/index.md">从零开始的解析HWDB1.1数据之路</a></p>

<p>这里仅仅给出代码</p>

<pre><code class="language-python">class MyResize(object):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    def __call__(self, img):
        raw_width, raw_height = img.size
        ratio = min(self.height/raw_height, self.width/raw_width)
        twidth, theight = (min(int(ratio * raw_width), self.width - 15), min(int(ratio * raw_height), self.height - 15))
        img = img.resize((twidth, theight), Image.ANTIALIAS)
        # 拼接图片，补足边框 居中
        ret = Image.new(&#39;L&#39;,(self.width, self.height), 255)
        ret.paste(img, (int((self.width-twidth)/2),int((self.height-theight)/2)))
        return ret
</code></pre>

<p>值得一提的是transforms​中还有很多有用的变换方式，可以用来加强数据的噪声，提高模型的泛化能力。</p>

<p>每个变换看完之后，再来回顾一下一开始的定义方式。
<code>python
from torchvision import datasets
from torchvision import transforms,utils
train_data = datasets.ImageFolder(&#39;train_data&#39;, transform=transforms.Compose([
    MyResize(64,64),
    transforms.ToTensor()
]))</code></p>

<p>这里的含义便是从<q>train_data</q>加载数据自动表示label，并对每张图片先放缩到大小为(64, 64)，再将图片转化为Tensor类型。</p>

<p>那怎么从这个里面取出数据来训练呢？</p>

<h2>如何自动分批次读取数据</h2>

<p>又幸运的是，pyTorch也提供了和ImageFolder一起的分批次加载数据的方式，即DataLoader。</p>

<pre><code class="language-python">import torch.utils.data as Data
batch_size = 32
train_loader = Data.DataLoader(train_data,batch_size=batch_size, shuffle=True)
# train_data 是ImageFolder
</code></pre>

<p>就这样定义十分简单(</p>

<p>batch_size 就是每个批次的大小</p>

<p>shuffle代表是否随机据取数据，True为随机</p>

<p>用法如下</p>

<pre><code class="language-python">for x, y in train_loader:
    # x 是 data 就愉快地可以训练了
    # y 是 label
</code></pre>

<p>严格来说ImageFolder在初始化阶段不会真正地将数据加载到内存，只会标明数据的label和数据的位置。</p>

<p>在向DataLoader进行请求的时候ImageFolder会从硬盘读取数据，这样有效防止了爆内存的事情发生，当然不可避免地会拖慢图片加载的速度。</p>

<p>其实DataLoader有一个num_works参数可以多核读数据，但是windows好像有bug...</p>


    </div>
    <script>
      hljs.initHighlightingOnLoad()
    </script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.5.0/highlightjs-line-numbers.min.js"></script>
  <script>
      hljs.initHighlightingOnLoad();
      hljs.initLineNumbersOnLoad();
  </script>
  </body>
</html>